package com.yourenbang.lingyun.transaction.web.service.impl.fund;

import cn.hutool.core.date.LocalDateTimeUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.github.pagehelper.PageHelper;
import com.yourenbang.lingyun.constants.BizCodeMessages;
import com.yourenbang.lingyun.constants.Constant;
import com.yourenbang.lingyun.constants.enums.ChannelIdEnums;
import com.yourenbang.lingyun.constants.enums.FundBillType;
import com.yourenbang.lingyun.constants.enums.PlatNoEnums;
import com.yourenbang.lingyun.constants.enums.fund.AccountMainJournalType;
import com.yourenbang.lingyun.constants.enums.fund.AccountType;
import com.yourenbang.lingyun.constants.enums.mq.transaction.ChangeFundDTO;
import com.yourenbang.lingyun.core.exception.BizRuntimeException;
import com.yourenbang.lingyun.core.exception.SysBizException;
import com.yourenbang.lingyun.core.exception.UserBizException;
import com.yourenbang.lingyun.core.message.Result;
import com.yourenbang.lingyun.core.model.vo.PageVO;
import com.yourenbang.lingyun.core.support.PageFrom;
import com.yourenbang.lingyun.core.util.*;
import com.yourenbang.lingyun.core.util.encrypt.AesEncryptUtil;
import com.yourenbang.lingyun.domain.AllyChannel;
import com.yourenbang.lingyun.domain.AllyChannelExample;
import com.yourenbang.lingyun.domain.FundAccount;
import com.yourenbang.lingyun.domain.UserBase;
import com.yourenbang.lingyun.mapper.AllyChannelMapper;
import com.yourenbang.lingyun.mapper.UserBaseMapper;
import com.yourenbang.lingyun.transaction.config.DingdingConfig;
import com.yourenbang.lingyun.transaction.constant.ChangePlanStatus;
import com.yourenbang.lingyun.transaction.constant.ChangeTypeConstant;
import com.yourenbang.lingyun.transaction.mapper.TransactionFundAccountChangePlanMapper;
import com.yourenbang.lingyun.transaction.mapper.TransactionFundAccountMapper;
import com.yourenbang.lingyun.transaction.mapper.TransactionFundAccountWithdrawPaymentMapper;
import com.yourenbang.lingyun.transaction.mapper.TransactionFundBillMapper;
import com.yourenbang.lingyun.transaction.web.domain.dto.ChangeFundAccountV2DTO;
import com.yourenbang.lingyun.transaction.web.domain.dto.fund.*;
import com.yourenbang.lingyun.transaction.web.domain.po.FundAccountChangePlan;
import com.yourenbang.lingyun.transaction.web.domain.po.FundAccountWithdrawPayment;
import com.yourenbang.lingyun.transaction.web.domain.po.TsFundAccount;
import com.yourenbang.lingyun.transaction.web.domain.vo.JournalInfoVO;
import com.yourenbang.lingyun.transaction.web.domain.vo.pay.WithdrawInAmountFeeVO;
import com.yourenbang.lingyun.transaction.web.service.CommonService;
import com.yourenbang.lingyun.transaction.web.service.fund.AccountService;
import com.yourenbang.lingyun.transaction.web.service.fund.FundService;
import com.yourenbang.lingyun.transaction.web.service.fund.fundopt.*;
import com.yourenbang.lingyun.transaction.web.service.impl.migrate.AbstractMigrateService;
import com.yourenbang.lingyun.transaction.web.service.impl.pay.withdraw.WithdrawAccountConfig;
import com.yourenbang.transaction.api.model.dto.AddAccountDTO;
import com.yourenbang.transaction.api.model.dto.manage.TopupSysAccountDTO;
import com.yourenbang.transaction.api.model.vo.FundPayBusVO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;


@Slf4j
@Service
public class FundServiceImpl extends AbstractMigrateService implements FundService {

    @Resource
    private TransactionFundAccountMapper transactionFundAccountMapper;
    @Resource
    private AllyChannelMapper allyChannelMapper;

    @Resource
    private FundOptAddService fundOptAddService;

    @Resource
    private FundOptSubtractService fundOptSubtractService;

    @Resource
    private FundOptReversalService fundOptReversalService;

    @Resource
    private FundOptDepositService fundOptDepositService;

    @Resource
    private FundOptCancelService fundOptCancelService;
    @Resource
    FundOptUndepositService fundOptUndepositService;

    @Resource
    private CommonService commonService;

    @Autowired
    AccountService accountService;

    @Resource
    private TransactionFundAccountWithdrawPaymentMapper fundAccountWithdrawPaymentMapper;

    @Resource
    private TransactionFundAccountChangePlanMapper fundAccountChangePlanMapper;
    @Resource
    private TransactionFundBillMapper transactionFundBillMapper;
    @Resource
    private DingdingConfig dingdingConfig;
    @Resource
    UserBaseMapper userBaseMapper;

    @Override
    public Result addFund(String param) {
        try {
            ChangeFundDTO changeFundDTO = getChangeFundAccountDTO(param);
            return add(changeFundDTO);
        } catch (Exception e) {
            KLog.error("[通用加账]异常:{}", e.getMessage(), e);
            KLog.error(e, "[通用加账]异常");
            return Result.builder().fail(BizCodeMessages.UNKNOWN_ERROR, e.getMessage());
        }
    }

    @Override
    public Result addFund(ChangeFundDTO changeFundDTO) {
        return add(changeFundDTO);
    }

    @Override
    public Result add(String param) {
        try {
            ChangeFundDTO changeFundDTO = getChangeFundAccountDTO(param);
            return add(changeFundDTO);
        } catch (Exception e) {
            KLog.error("[通用加账]异常:{}", e.getMessage(), e);
            return Result.builder().fail(BizCodeMessages.UNKNOWN_ERROR, e.getMessage());
        }
    }

    @Override
    public Result add(TopupSysAccountDTO topupSysAccountDTO) {

        //通过userbaseid为空判断是否为系统资金账户,不严谨,通过accounttype
        FundAccount toFundAccount = transactionFundAccountMapper.getFundAccountById(topupSysAccountDTO.getFundAccountId());


        //充值系统财务账户的话发起账户为0,其他发起为系统财务账户
        AccountType toAccountType = AccountType.getAccountTypeByFullCode(toFundAccount.getAccountType());
        JournalInfoVO journalInfoVO;
        FundBillType fundBillType = null;
        boolean showFlag = true;

        if (AccountType.SYS_V2_OUT.isEquals(toAccountType)
                || AccountType.SYS_V2_WITHDRAW.isEquals(toAccountType)
        ) {
            journalInfoVO = new JournalInfoVO(
                    null,
                    toAccountType,
                    AccountMainJournalType.SYS_ADD

            );
        } else if (AccountType.SYS_BOSS_OUT.isEquals(toAccountType)) {
            //BOSS出账账户
            journalInfoVO = new JournalInfoVO(
                    AccountType.SYS_V2_OUT,
                    toAccountType,
                    AccountMainJournalType.BOSS_ADD
            );
            fundBillType = FundBillType.TYPE_450;
            //出账账户不显示账单
            showFlag = false;
        } else if (AccountType.SYS_BOSS_WITHDRAW.isEquals(toAccountType)) {
            //BOSS提现账户
            journalInfoVO = new JournalInfoVO(
                    AccountType.SYS_V2_WITHDRAW,
                    toAccountType,
                    AccountMainJournalType.BOSS_ADD
            );
            fundBillType = FundBillType.TYPE_450;
        } else {
            throw new UserBizException(BizCodeMessages.PARAMS_ERROR, "资金账号有误");
        }

        ChangeFundAccountV2DTO changeFundAccountV2DTO = new ChangeFundAccountV2DTO();
        changeFundAccountV2DTO.setUserBaseId(toFundAccount.getUserBaseId());
        changeFundAccountV2DTO.setOrderNo(Generator.nextUUID());
        changeFundAccountV2DTO.setPlatNo(toFundAccount.getPlatNo());
        changeFundAccountV2DTO.setChangeAmount(topupSysAccountDTO.getTopupAmount());
        changeFundAccountV2DTO.setJournalInfo(journalInfoVO);
        changeFundAccountV2DTO.setChannelId(ChannelIdEnums.no.getCode());
        changeFundAccountV2DTO.setFundBillType(fundBillType);
        changeFundAccountV2DTO.setShowFlag(showFlag);

        return add(changeFundAccountV2DTO);
    }

    @Override
    public Result add(ChangeFundDTO changeFundDTO) {

        ChangeFundAccountV2DTO changeFundAccountV2DTO = convertToChangeFundAccountDto(true, changeFundDTO);

        return add(changeFundAccountV2DTO);
    }

    @Override
    public Result add(ChangeFundAccountV2DTO changeFundAccountV2DTO) {

        if (repeatSubmitCheck(changeFundAccountV2DTO)) {
            return Result.builder().fail(BizCodeMessages.VALIDATOR_REPEAT_SUBMIT);
        }

        //资金发起方
        FundAccount fromFundAccount;
        FundAccount toFundAccount;
        //是否给系统账号加帐,系统账号加帐无资金发起方


        if (AccountType.SYS_V2_WITHDRAW.isEquals(changeFundAccountV2DTO.getJournalInfo().getToAccountType())
                || AccountType.SYS_V2_OUT.isEquals(changeFundAccountV2DTO.getJournalInfo().getToAccountType())
        ) {

            //给平台、boss资金账号加帐,无需记录资金支出账户
            fromFundAccount = null;

        } else if (AccountType.ALLY_FROM_BOSS.isEquals(changeFundAccountV2DTO.getJournalInfo().getToAccountType())) {

            //盟友资金账号,资金由boss支出部分加帐
            Integer fullFromAccountType = AccountType.getFullCode(changeFundAccountV2DTO.getJournalInfo().getFromAccountType(),
                    changeFundAccountV2DTO.getPlatNo());

            fromFundAccount = transactionFundAccountMapper.getBossAccountByToUserInfo(fullFromAccountType, changeFundAccountV2DTO.getUserBaseId());
            AssertUtils.isNotNull(fromFundAccount, BizCodeMessages.DB_QUERY_ERR);

        } else if (AccountType.MERCHANT_FROM_BOSS.isEquals(changeFundAccountV2DTO.getJournalInfo().getToAccountType())) {

            //盟友资金账号,资金由boss支出部分加帐
            Integer fullFromAccountType = AccountType.getFullCode(changeFundAccountV2DTO.getJournalInfo().getFromAccountType(),
                    changeFundAccountV2DTO.getPlatNo());

            fromFundAccount = transactionFundAccountMapper.getBossAccountByMerchantInfo(fullFromAccountType, changeFundAccountV2DTO.getUserBaseId());
            AssertUtils.isNotNull(fromFundAccount, BizCodeMessages.DB_QUERY_ERR);

        } else if (AccountType.ALLY_V2.isEquals(changeFundAccountV2DTO.getJournalInfo().getToAccountType())
                || AccountType.MER.isEquals(changeFundAccountV2DTO.getJournalInfo().getToAccountType())
                || AccountType.SYS_BOSS_OUT.isEquals(changeFundAccountV2DTO.getJournalInfo().getToAccountType())
                || AccountType.SYS_BOSS_WITHDRAW.isEquals(changeFundAccountV2DTO.getJournalInfo().getToAccountType())
        ) {

            //盟友资金账号,资金由平台部分支出部分加帐
            Integer fullFromAccountType = AccountType.getFullCode(changeFundAccountV2DTO.getJournalInfo().getFromAccountType(),
                    changeFundAccountV2DTO.getPlatNo());
            fromFundAccount = transactionFundAccountMapper.getSysAccountByType(fullFromAccountType);
            AssertUtils.isNotNull(fromFundAccount, BizCodeMessages.DB_QUERY_ERR);

        } else {
            throw new UserBizException(BizCodeMessages.PARAMS_ERROR, "资金账户类型异常");
        }


        //资金接收方
        toFundAccount = getToFundAccount(changeFundAccountV2DTO);
        AssertUtils.isNotNull(toFundAccount, BizCodeMessages.TRANSACTION_FUNDACCOUNT_NONE);

        checkOrCreateFundAccountChangePlan(fromFundAccount, toFundAccount, changeFundAccountV2DTO);

        try {
            fundOptAddService.add(fromFundAccount, toFundAccount, changeFundAccountV2DTO);
        } catch (BizRuntimeException ex) {
            //更新失败
            return Result.builder().fail(ex);
        }

        return Result.builder().success();
    }


    private FundAccount getToFundAccount(ChangeFundAccountV2DTO changeFundAccountV2DTO) {


        Integer toAccountTypeFullCode = changeFundAccountV2DTO.getJournalInfo().getToAccountType().getFullCode(changeFundAccountV2DTO.getPlatNo());

        FundAccount toAccountByType = transactionFundAccountMapper.getUserAccountByType(
                changeFundAccountV2DTO.getUserBaseId(), toAccountTypeFullCode);

        if (Objects.isNull(toAccountByType)) {
            UserBase userBase = userBaseMapper.selectByPrimaryKey(changeFundAccountV2DTO.getUserBaseId());
            //创建账户
            AddAccountDTO addAccountDTO=new AddAccountDTO();
            addAccountDTO.setAccountName(userBase.getUserName());
            addAccountDTO.setUserBaseId(changeFundAccountV2DTO.getUserBaseId());
            addAccountDTO.setAccountType(changeFundAccountV2DTO.getJournalInfo().getToAccountType().getFullCode(PlatNoEnums.YXY.getCode()));
            addAccountDTO.setAccountTypeName(changeFundAccountV2DTO.getJournalInfo().getToAccountType().getAccountName());
            addAccountDTO.setChannelId(ChannelIdEnums.no.getCode());
            addAccountDTO.setPlatNo(PlatNoEnums.YXY.getCode());

            Result<FundAccount> createResult = accountService.addAccount(addAccountDTO);
            log.info("账户不存在添加账户，{}，{}，{}", changeFundAccountV2DTO, addAccountDTO,createResult);
            if (createResult.isSuccess()) {
                if (changeFundAccountV2DTO.getJournalInfo().getToAccountType().isEqualsFullAccountNo(AccountType.MERCHANT_FROM_BOSS.getFullCode(PlatNoEnums.YXY.getCode()))) {
                    // 同时创建21100310 商户平台钱包 21100312 商户BOSS钱包两个账户哈
                    addAccountDTO.setAccountType(AccountType.MER.getFullCode(PlatNoEnums.YXY.getCode()));
                    addAccountDTO.setAccountTypeName(AccountType.MER.getAccountName());
                    accountService.addAccount(addAccountDTO);
                }
                return createResult.getData();
            }
            return null;
        }
        return toAccountByType;

    }


    private boolean repeatSubmitCheck(ChangeFundAccountV2DTO changeFundAccountV2DTO) {
        //加账订单号传了fundbilltype changeplan的插入订单号需要拼接fundbilltype, 账单明细不用拼接
        //重复提交
        String fundBillTypeName = "";
        if (Objects.nonNull(changeFundAccountV2DTO.getFundBillType())) {
            fundBillTypeName = changeFundAccountV2DTO.getFundBillType().getName();
        }

        String repeatKey = com.yourenbang.lingyun.core.util.StringUtils.join(new String[]{"changeFundAccount", changeFundAccountV2DTO.getUserBaseId(),
                changeFundAccountV2DTO.getOrderNo(), fundBillTypeName}, ":");

        return commonService.repeatSubmit(repeatKey, 60);
    }


    @Override
    public Result subtractFund(String param) {
        try {
            ChangeFundDTO changeFundDTO = getChangeFundAccountDTO(param);
            return subtractFund(changeFundDTO);
        } catch (Exception e) {
            KLog.error("[通用减账]异常:{}", e.getMessage(), e);
            return Result.builder().fail(BizCodeMessages.UNKNOWN_ERROR, e.getMessage());
        }
    }

    @Override
    public Result subtractFund(ChangeFundDTO changeFundDTO) {
        return subtract(changeFundDTO);
    }

    @Override
    public Result subtract(String param) {
        try {
            ChangeFundDTO changeFundDTO = getChangeFundAccountDTO(param);
            return subtractFund(changeFundDTO);
        } catch (Exception e) {
            KLog.error("[通用减账]异常:{}", e.getMessage(), e);
            return Result.builder().fail(BizCodeMessages.UNKNOWN_ERROR, e.getMessage());
        }
    }

    @Override
    public Result subtract(ChangeFundDTO changeFundDTO) {

        ChangeFundAccountV2DTO changeFundAccountV2DTO = convertToChangeFundAccountDto(false, changeFundDTO);

        return subtract(changeFundAccountV2DTO);
    }

    @Override
    public Result subtract(ChangeFundAccountV2DTO changeFundAccountV2DTO) {

        if (repeatSubmitCheck(changeFundAccountV2DTO)) {
            return Result.builder().fail(BizCodeMessages.VALIDATOR_REPEAT_SUBMIT);
        }

        //资金发起方
        Integer fullFromAccountType = AccountType.getFullCode(changeFundAccountV2DTO.getJournalInfo().getFromAccountType(),
                changeFundAccountV2DTO.getPlatNo());
        FundAccount fromFundAccount = transactionFundAccountMapper.getSysAccountByType(fullFromAccountType);
        AssertUtils.isNotNull(fromFundAccount, BizCodeMessages.TRANSACTION_ACCOUNT_NONE);

        //资金接收方
        FundAccount toFundAccount = getToFundAccount(changeFundAccountV2DTO);

        checkOrCreateFundAccountChangePlan(fromFundAccount, toFundAccount, changeFundAccountV2DTO);

        Result result = fundOptSubtractService.subtract(fromFundAccount, toFundAccount, changeFundAccountV2DTO);

        if (result.isFail()) {
            if (BizCodeMessages.TRANSACTION_SUBTRACT_ACCOUNT_FAIL.isEquals(result.getCode())) {
                //余额不足,钉钉预警
                String dingMark = "【账户余额不足预警】" + PlatNoEnums.getPlatNo(changeFundAccountV2DTO.getPlatNo()).getName() +
                        changeFundAccountV2DTO.getJournalInfo().getFromAccountType().getAccountName();
                dingMark += result.getDesc();
                sendDingdingAlarm(dingMark);
            }
        }

        return result;
    }

    @Override
    public Result reversal(ChangeFundAccountV2DTO changeFundAccountV2DTO) {

        if (repeatSubmitCheck(changeFundAccountV2DTO)) {
            return Result.builder().fail(BizCodeMessages.VALIDATOR_REPEAT_SUBMIT);
        }

        // 资金发起方
        Integer fromAccountTypeFullCode = changeFundAccountV2DTO.getJournalInfo().getFromAccountType().getFullCode(changeFundAccountV2DTO.getPlatNo());
        FundAccount fromFundAccount = transactionFundAccountMapper.getUserAccountByType(
                changeFundAccountV2DTO.getUserBaseId(), fromAccountTypeFullCode);
        AssertUtils.isNotNull(fromFundAccount, BizCodeMessages.TRANSACTION_ACCOUNT_NONE);

        // 资金接收方
        // 资金接收方可为平台、boss.当资金接收方为boss时,不同的用户boss可能不一样,需要根据发起方信息查询自己的boss
        Integer fullToAccountType = AccountType.getFullCode(changeFundAccountV2DTO.getJournalInfo().getToAccountType(),
                changeFundAccountV2DTO.getPlatNo());
        FundAccount toFundAccount = null;

        if (AccountType.SYS_V2_OUT.isEquals(changeFundAccountV2DTO.getJournalInfo().getToAccountType())) {

            toFundAccount = transactionFundAccountMapper.getSysAccountByType(fullToAccountType);

        } else if (AccountType.SYS_BOSS_OUT.isEquals(changeFundAccountV2DTO.getJournalInfo().getToAccountType())) {

            if (AccountType.ALLY_FROM_BOSS.isEquals(changeFundAccountV2DTO.getJournalInfo().getFromAccountType())) {

                toFundAccount = transactionFundAccountMapper.getBossAccountByToUserInfo(fullToAccountType, changeFundAccountV2DTO.getUserBaseId());
                AssertUtils.isNotNull(toFundAccount, BizCodeMessages.DB_QUERY_ERR);

            } else if (AccountType.MERCHANT_FROM_BOSS.isEquals(changeFundAccountV2DTO.getJournalInfo().getFromAccountType())) {

                toFundAccount = transactionFundAccountMapper.getBossAccountByMerchantInfo(fullToAccountType, changeFundAccountV2DTO.getUserBaseId());
                AssertUtils.isNotNull(toFundAccount, BizCodeMessages.DB_QUERY_ERR);

            }

        }

        if (Objects.isNull(toFundAccount)) {
            throw new UserBizException(BizCodeMessages.PARAMS_ERROR, "资金账户类型异常");
        }


        checkOrCreateFundAccountChangePlan(fromFundAccount, toFundAccount, changeFundAccountV2DTO);

        return fundOptReversalService.reversal(fromFundAccount, toFundAccount, changeFundAccountV2DTO);

    }

    @Override
    public Result<FundPayBusVO> deposit(ChangeFundAccountV2DTO changeFundAccountV2DTO) {

        KLog.info("【提现】可提余额冻结, 入参:{}", changeFundAccountV2DTO);

        Integer fullToAccountType = changeFundAccountV2DTO.getJournalInfo().getToAccountType().getFullCode(changeFundAccountV2DTO.getPlatNo());
        // 是否为余额支付
        boolean isBalancePay = false;

        FundAccount fromFundAccount;
        FundAccount toFundAccount = null;

        if (AccountType.ALLY_FROM_BOSS.isEquals(changeFundAccountV2DTO.getJournalInfo().getToAccountType())) {

            //盟友资金账号,资金由boss支出部分加帐
            Integer fullFromAccountType = AccountType.getFullCode(changeFundAccountV2DTO.getJournalInfo().getFromAccountType(),
                    changeFundAccountV2DTO.getPlatNo());

            fromFundAccount = transactionFundAccountMapper.getBossAccountByToUserInfo(fullFromAccountType, changeFundAccountV2DTO.getUserBaseId());
            AssertUtils.isNotNull(fromFundAccount, BizCodeMessages.TRANSACTION_FUNDACCOUNT_NONE);

        } else if (AccountType.ALLY_V2.isEquals(changeFundAccountV2DTO.getJournalInfo().getToAccountType())) {

            //盟友资金账号,资金由平台部分支出部分加帐
            Integer fullFromAccountType = AccountType.getFullCode(changeFundAccountV2DTO.getJournalInfo().getFromAccountType(),
                    changeFundAccountV2DTO.getPlatNo());
            fromFundAccount = transactionFundAccountMapper.getSysAccountByType(fullFromAccountType);
            AssertUtils.isNotNull(fromFundAccount, BizCodeMessages.TRANSACTION_FUNDACCOUNT_NONE);

        } else if (AccountType.MERCHANT_FROM_BOSS.isEquals(changeFundAccountV2DTO.getJournalInfo().getToAccountType())) {

            //商户资金账号,资金由boss支出部分加帐
            Integer fullFromAccountType = AccountType.getFullCode(changeFundAccountV2DTO.getJournalInfo().getFromAccountType(),
                    changeFundAccountV2DTO.getPlatNo());

            fromFundAccount = transactionFundAccountMapper.getBossAccountByMerchantInfo(fullFromAccountType, changeFundAccountV2DTO.getUserBaseId());
            AssertUtils.isNotNull(fromFundAccount, BizCodeMessages.TRANSACTION_FUNDACCOUNT_NONE);

        } else if (AccountType.SYS_V2_WITHDRAW.isEquals(changeFundAccountV2DTO.getJournalInfo().getToAccountType())) {

            fromFundAccount = transactionFundAccountMapper.getUserAccountByType(changeFundAccountV2DTO.getUserBaseId(),
                    changeFundAccountV2DTO.getJournalInfo().getFromAccountType().getFullCode(changeFundAccountV2DTO.getPlatNo()));

            isBalancePay = true;

            toFundAccount = transactionFundAccountMapper.getSysAccountByType(fullToAccountType);
            AssertUtils.isNotNull(fromFundAccount, BizCodeMessages.TRANSACTION_FUNDACCOUNT_NONE);

        } else if (AccountType.SYS_BOSS_WITHDRAW.isEquals(changeFundAccountV2DTO.getJournalInfo().getToAccountType())) {

            fromFundAccount = transactionFundAccountMapper.getUserAccountByType(changeFundAccountV2DTO.getUserBaseId(),
                    changeFundAccountV2DTO.getJournalInfo().getFromAccountType().getFullCode(changeFundAccountV2DTO.getPlatNo()));

            isBalancePay = true;

            toFundAccount = transactionFundAccountMapper.getBossAccountByToUserInfo(fullToAccountType, changeFundAccountV2DTO.getUserBaseId());
            AssertUtils.isNotNull(fromFundAccount, BizCodeMessages.TRANSACTION_FUNDACCOUNT_NONE);

        } else {
            throw new UserBizException(BizCodeMessages.PARAMS_ERROR, "资金账户类型异常");
        }


        FundPayBusVO fundPayBusVO = new FundPayBusVO();

        if (isBalancePay) {

            // 用户钱包余额支付,无需扣除手续费
            fundPayBusVO.setFee(BigDecimal.ZERO);
            fundPayBusVO.setInAmount(changeFundAccountV2DTO.getChangeAmount());
            fundPayBusVO.setFundAccountId(fromFundAccount.getFundAccountId());

            changeFundAccountV2DTO.setFundBillType(FundBillType.TYPE_300);
        } else {

            toFundAccount = transactionFundAccountMapper.getUserAccountByType(changeFundAccountV2DTO.getUserBaseId(), fullToAccountType);

            //设置 到账金额,手续费
            WithdrawInAmountFeeVO feeVO = WithdrawAccountConfig.withdrawInAmountFeeVO(toFundAccount.getAccountType());
            BigDecimal fee = changeFundAccountV2DTO.getChangeAmount().multiply(feeVO.getWithdrawRate()).setScale(2, RoundingMode.HALF_UP).add(feeVO.getWithdrawFixedAmt());
            fundPayBusVO.setFee(fee);
            fundPayBusVO.setInAmount(changeFundAccountV2DTO.getChangeAmount().subtract(fee));
            fundPayBusVO.setFundAccountId(toFundAccount.getFundAccountId());
            if (fundPayBusVO.getInAmount().compareTo(BigDecimal.ZERO) < 0) {
                return Result.builder().fail(BizCodeMessages.PARAMETER_ERR, "提现金额不应低于10元");
            }

            changeFundAccountV2DTO.setFundBillType(FundBillType.TYPE_200);
        }

        if (Objects.isNull(toFundAccount)) {
            KLog.warn("【提现】可提余额冻结, 用户账户不存在, 入参:{}", changeFundAccountV2DTO);
            return Result.builder().fail(BizCodeMessages.TRANSACTION_ACCOUNT_NONE, "用户账户不存在");
        }

        if (!toFundAccount.getAccountStatus().equals(TsFundAccount.ACCOUNT_STATUS_ON)) {
            KLog.warn("【提现】可提余额冻结, 用户账户状态为[{}], 入参:{}", toFundAccount.getAccountStatus(), changeFundAccountV2DTO);
            return Result.builder().fail(BizCodeMessages.TRANSACTION_ACCOUNT_STATUS_OFF, "用户账户状态异常");
        }


        checkOrCreateFundAccountChangePlan(fromFundAccount, toFundAccount, changeFundAccountV2DTO);

        Result<FundAccountChangePlan> result;
        if (isBalancePay) {
            result = fundOptDepositService.depositBalance(fromFundAccount, toFundAccount, changeFundAccountV2DTO);
        } else {
            result = fundOptDepositService.deposit(fromFundAccount, toFundAccount, changeFundAccountV2DTO);
        }
        if (result.isFail())
            return Result.builder().fail();


        FundAccountChangePlan fundAccountChangePlan = result.getData();
        fundPayBusVO.setFundAccountChangePlanId(fundAccountChangePlan.getFundAccountChangePlanId());

        return Result.builder().success(fundPayBusVO);

    }

    @Override
    public Result cancelByFundAccountWithdrawPaymentId(Integer fundAccountWithdrawPaymentId, Short platNo) {

        KLog.info("【提现】归还冻结金额, fundAccountWithdrawPaymentId:{}", fundAccountWithdrawPaymentId);

        FundAccountWithdrawPayment withdrawPayment = fundAccountWithdrawPaymentMapper.selectByPrimaryKey(platNo, fundAccountWithdrawPaymentId);
        AssertUtils.isNotNull(withdrawPayment, BizCodeMessages.DB_QUERY_ERR);

        FundAccountChangePlan fundAccountChangePlan = fundAccountChangePlanMapper.getByOrderId(platNo,
                withdrawPayment.getOrderSn());
        AssertUtils.isNotNull(fundAccountChangePlan, BizCodeMessages.TRANSACTION_CHANGE_PLAN_ERROR);


        return cancel(fundAccountChangePlan);
    }

    @Override
    public Result cancelByOrderNo(String orderNo) {
        FundAccountChangePlan fundAccountChangePlan = fundAccountChangePlanMapper.getByOrderId(PlatNoEnums.YXY.getCode(), orderNo);

        if (Objects.isNull(fundAccountChangePlan)) {
            return Result.builder().fail(BizCodeMessages.TRANSACTION_CHANGE_PLAN_NOT_EXIST);
        }

        if (ChangePlanStatus.CHANGING != fundAccountChangePlan.getAccountChangePlanStatus()) {
            return Result.builder().fail(BizCodeMessages.TRANSACTION_CHANGE_PLAN_ERROR, "资金变更计划状态非法");
        }

        return cancel(fundAccountChangePlan);
    }

    @Override
    public Result cancelByFundAccountChangePlanId(Integer fundAccountChangePlanId, Short platNo) {
        FundAccountChangePlan fundAccountChangePlan = fundAccountChangePlanMapper.getById(platNo, fundAccountChangePlanId);
        AssertUtils.isNotNull(fundAccountChangePlan, BizCodeMessages.TRANSACTION_CHANGE_PLAN_ERROR);

        return cancel(fundAccountChangePlan);
    }

    private Result cancel(FundAccountChangePlan fundAccountChangePlan) {

        if (AccountMainJournalType.BALANCE_PAY.isEquals(fundAccountChangePlan.getAccountMainJournalType())) {
            return fundOptCancelService.cancelBalancePay(fundAccountChangePlan);
        }

        return fundOptCancelService.cancel(fundAccountChangePlan);

    }

    @Override
    public Result undepositByFundAccountWithdrawPaymentId(Integer fundAccountWithdrawPaymentId, Short platNo) {

        KLog.info("【提现】解冻金额, fundAccountWithdrawPaymentId:{}", fundAccountWithdrawPaymentId);

        FundAccountWithdrawPayment withdrawPayment = fundAccountWithdrawPaymentMapper.selectByPrimaryKey(platNo, fundAccountWithdrawPaymentId);
        AssertUtils.isNotNull(withdrawPayment, BizCodeMessages.DB_QUERY_ERR);

        FundAccountChangePlan fundAccountChangePlan = fundAccountChangePlanMapper.getByOrderId(platNo,
                withdrawPayment.getOrderSn());
        AssertUtils.isNotNull(fundAccountChangePlan, BizCodeMessages.DB_QUERY_ERR);

//        if (Objects.isNull(changePlan)) {
//            KLog.warn("【提现】归还冻结金额异常, 资金变动计划为空:{}", fundAccountWithdrawPaymentId);
//            throw new BizRuntimeException(BizCodeMessages.UNKNOWN_ERROR, "资金变动计划为空");
//        }

        return undeposit(fundAccountChangePlan);
    }

    @Override
    public Result undepositByFundAccountChangePlanId(Integer fundAccountWithdrawPaymentId, Short platNo) {

        FundAccountChangePlan fundAccountChangePlan = fundAccountChangePlanMapper.getById(platNo, fundAccountWithdrawPaymentId);
        AssertUtils.isNotNull(fundAccountChangePlan, BizCodeMessages.DB_QUERY_ERR);

        return undeposit(fundAccountChangePlan);
    }

    @Override
    public Result<IncomeStatisticsTotalResponse> statisticsTotal(IncomeStatisticsTotalRequest data) {
        if (StringUtils.isEmpty(data.getStatisticsTime())) {
            if (data.getStatisticsType() == 1) { // 是按月统计
                data.setStatisticsTime(LocalDateTimeUtil.format(LocalDateTime.now(), "yyyy"));
            } else { // 2按天统计
                data.setStatisticsTime(LocalDateTimeUtil.format(LocalDateTime.now(), "yyyyMM"));
            }
        }
        List<IncomeStatisticsTotalDTO> statisticsTotalByTime = transactionFundBillMapper.statisticsTotalByTime(data);
        List<IncomeStatisticsTotalResponse.ListElement> elements = statisticsTotalByTime.stream().map(item -> {
            IncomeStatisticsTotalResponse.ListElement element = new IncomeStatisticsTotalResponse.ListElement();
            element.setAmount(item.getAmount());
            if (data.getStatisticsType() == 1) { // 是按月统计
                element.setTitle(StringUtils.left(item.getTitle(), 4) + "-" + StringUtils.right(item.getTitle(), 2));
            } else {
                element.setTitle(StringUtils.left(item.getTitle(), 4) + "-" + StringUtils.substring(item.getTitle(), 4, 6)
                        + "-" + StringUtils.right(item.getTitle(), 2));
            }
            return element;
        }).collect(Collectors.toList());

        List<IncomeStatisticsTotalDTO> statisticsTotalByType = transactionFundBillMapper.statisticsTotalByType(data);
        IncomeStatisticsTotalResponse.Summary summary = new IncomeStatisticsTotalResponse.Summary();
        if (CollectionUtils.isNotEmpty(statisticsTotalByType)) {
            /*
              明细数据统计：
              分红收益：fund_bill_id表中type（460,470,270,271,450）并且account_main_journal_type in =360
              奖励收益：fund_bill_id表中type（113,110,170,450）并且account_main_journal_type in（320,330,340）
              其它：fund_bill_id表中type（442）并且account_main_journal_type in =360
             */
            statisticsTotalByType.forEach(item -> {
                summary.setTotalAmount(summary.getTotalAmount().add(item.getAmount()));

                if (AccountMainJournalType.ALLY_ACTIVATE.isEquals(item.getTitle()) ||
                        AccountMainJournalType.ALLY_REACH.isEquals(item.getTitle()) ||
                        AccountMainJournalType.ALLY_PROFIT.isEquals(item.getTitle())) {
                    summary.setProfitAmount(summary.getProfitAmount().add(item.getAmount()));
                } else if (AccountMainJournalType.ALLY_ACTIVITY.isEquals(item.getTitle())) {
                    if (FundBillType.TYPE_442.isEquals(item.getType())) {
                        summary.setOtherAmount(summary.getOtherAmount().add(item.getAmount()));
                    } else {
                        summary.setReversalAmount(summary.getReversalAmount().add(item.getAmount()));
                    }
                }
            });
        }

        IncomeStatisticsTotalResponse response = new IncomeStatisticsTotalResponse();
        response.setList(elements);
        response.setSummary(summary);
        return Result.builder().success(response);
    }

    @Override
    public Result<PageVO> statisticsDetail(IncomeStatisticsDetailRequest data) {
        if (StringUtils.isNotBlank(data.getStartTime())) {
            data.setStartTime(StringUtils.replace(data.getStartTime(), "-", ""));
            if (StringUtils.length(data.getStartTime()) == 6) {
                data.setStartTime(data.getStartTime() + "01");
            }
        }
        if (StringUtils.isNotBlank(data.getEndTime())) {
            data.setEndTime(StringUtils.replace(data.getEndTime(), "-", ""));
            if (StringUtils.length(data.getEndTime()) == 6) {
                data.setEndTime(data.getEndTime() + "31");
            }
        }
        PageFrom.startPage(data.getPageNum(), data.getPageSize());
        List<IncomeStatisticsDetailResponse> list = transactionFundBillMapper.statisticsDetail(data);
        return Result.builder().successPage(list);
    }

    private Result undeposit(FundAccountChangePlan fundAccountChangePlan) {

        if (AccountMainJournalType.BALANCE_PAY.isEquals(fundAccountChangePlan.getAccountMainJournalType())) {
            return fundOptUndepositService.undepositBalancePay(fundAccountChangePlan);
        }

        return fundOptUndepositService.undeposit(fundAccountChangePlan);
    }

    private ChangeFundDTO getChangeFundAccountDTO(String param) {
        JSONObject jbc = JSON.parseObject(param);
        if (StringUtils.isBlank(jbc.getString("data"))) {
            KLog.warn("[通用加账]快收传入数据异常,入参:{}", param);
            throw new SysBizException(BizCodeMessages.VALIDATOR_ERR, "快收传入数据解析异常");
        }

        String decryptStr = AesEncryptUtil.base64AesDecrypt(jbc.getString("data"));
        KLog.info("[通用加账]解密后的数据:{}, 入参:{}", decryptStr, param);

        ChangeFundDTO changeFundDTO = JSON.parseObject(decryptStr, ChangeFundDTO.class);
        if (Objects.isNull(changeFundDTO)) {
            KLog.warn("[通用加账]读取数据失败, 解密后的数据:{} ,入参:{}", decryptStr, param);
            throw new SysBizException(BizCodeMessages.UNKNOWN_ERROR, "读取数据失败");
        }
        if (Objects.isNull(changeFundDTO.getAccountMainJournalType())) {
            KLog.warn("[通用加账]资金类型为空，请尽快更换:{} ", changeFundDTO);
        }
        return changeFundDTO;
    }

    /**
     * @param isAdd         true 加帐 false 减帐
     * @param changeFundDTO
     * @return
     */
    private ChangeFundAccountV2DTO convertToChangeFundAccountDto(boolean isAdd, ChangeFundDTO changeFundDTO) {

        Short platNo = Short.valueOf(StringUtils.left(changeFundDTO.getAccountType().toString(), 2));

        AccountType toAccountType = AccountType.getAccountTypeByFullCode(changeFundDTO.getAccountType());
        AccountMainJournalType accountMainJournalType = AccountMainJournalType.getMainJournalTypeByCode(changeFundDTO.getAccountMainJournalType());

        JournalInfoVO journalVO;
        if (isAdd) {
            //加帐
            journalVO = ChangeTypeConstant.getAddJournalTypeV2(toAccountType,
                    accountMainJournalType);
        } else {
            //减帐
            journalVO = ChangeTypeConstant.getSubtractJournalTypeV2(toAccountType,
                    accountMainJournalType);
        }

        FundBillType fundBillType = FundBillType.getUserBillType(changeFundDTO.getBillType());


        ChangeFundAccountV2DTO changeFundAccountV2DTO = new ChangeFundAccountV2DTO();
        changeFundAccountV2DTO.setUserBaseId(changeFundDTO.getUserBaseId());
        changeFundAccountV2DTO.setFundBillType(fundBillType);
        changeFundAccountV2DTO.setChangeAmount(changeFundDTO.getChangeAmount());
        changeFundAccountV2DTO.setChannelId(ChannelIdEnums.no.getCode());
        changeFundAccountV2DTO.setPlatNo(platNo);
        changeFundAccountV2DTO.setDetail(changeFundDTO.getDetail());
        changeFundAccountV2DTO.setOrderNo(changeFundDTO.getOrderNo());
        changeFundAccountV2DTO.setJournalInfo(journalVO);

        return changeFundAccountV2DTO;
    }

    private void sendDingdingAlarm(String msg) {
        DingdingUtils.sendToDingding(msg, dingdingConfig.getDingdingWarnToken());
    }

    private void checkOrCreateFundAccountChangePlan(FundAccount fromFundAccount, FundAccount toFundAccount,
                                                    ChangeFundAccountV2DTO changeFundAccountV2DTO) {

        FundAccountChangePlan changePlan = fundAccountChangePlanMapper.getByOrderId(toFundAccount.getPlatNo(),
                changeFundAccountV2DTO.getOrderNo());

        if (Objects.nonNull(changePlan) &&
                !(ChangePlanStatus.CHANGING == changePlan.getAccountChangePlanStatus()
                        || ChangePlanStatus.WAIT_TO_CHANGE == changePlan.getAccountChangePlanStatus())
        ) {
            //有变动记录且为已经完成不允许再次操作
            throw new UserBizException(BizCodeMessages.UNKNOWN_ERROR, "当前资金变动已完成或无效");
        }

        if (Objects.nonNull(changePlan)) {
            return;
        }


        changePlan = new FundAccountChangePlan(toFundAccount.getPlatNo());

        if (Objects.nonNull(fromFundAccount)) {
            //资金平台充值无资金发起方
            changePlan.setFundAccountId(fromFundAccount.getFundAccountId());
        }
        changePlan.setToFundAccountId(toFundAccount.getFundAccountId());


        changePlan.setAccountMainJournalType(changeFundAccountV2DTO.getJournalInfo().getAccountMainJournalType().getCode());
        changePlan.setAccountMainJournalTypeName(changeFundAccountV2DTO.getJournalInfo().getAccountMainJournalType().getName());

        changePlan.setOrderId(changeFundAccountV2DTO.getOrderNo());
        changePlan.setChangeAmount(changeFundAccountV2DTO.getChangeAmount());
        changePlan.setChannelId(changeFundAccountV2DTO.getChannelId());
        changePlan.setAccountChangePlanStatus(ChangePlanStatus.CHANGING);
        changePlan.setIsDel(Constant.NORMAL);

        int i = fundAccountChangePlanMapper.insert(changePlan);
        BizAssertUtils.assertOne(i, "添加资金变动计划失败");

    }

    private AllyChannel getBossByUserBaseId(String toUserBaseId) {

        AllyChannelExample allyChannelExample = new AllyChannelExample();
        allyChannelExample.createCriteria()
                .andUserBaseIdEqualTo(toUserBaseId)
                .andChannelIdEqualTo(ChannelIdEnums.YRB_LKL_EPOS.getCode());
        List<AllyChannel> allyChannels = allyChannelMapper.selectByExample(allyChannelExample);

        if (CollectionUtils.isEmpty(allyChannels)) {
            return null;
        }

        return allyChannels.get(0);
    }
}
